package com.gollum.jpa.dao.impl;


import com.gollum.jpa.dao.JpaDao;
import com.gollum.jpa.domain.Page;
import com.gollum.jpa.exception.JpaException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StopWatch;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.LongSupplier;


/**
 * 持久层dao封装接口实现
 *
 * @author Herther
 * @version 1.0.0
 * @ClassName JpaDaoImpl.java
 * @createTime 2022年08月06日 22:22:00
 */
public class JpaDaoImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements JpaDao<T, ID> {

    private static final Logger log = LoggerFactory.getLogger(JpaDaoImpl.class);

    private final EntityManager em;

    private JpaEntityInformation<T,ID> entityInformation;
    /** 一次性保存最大  **/
    private static final int MAX_SAVE_SIZE = 3000;

    /** 逻辑删除枚举值 **/
    private static final String DELETE_VALUE = "1";


    public CriteriaBuilder getCriteriaBuilder(){
        return em.getCriteriaBuilder();
    }

    public JpaDaoImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.em =entityManager;
        this.entityInformation = entityInformation;
    }

    public JpaDaoImpl(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.em = em;
    }

    public EntityManager getEntityManager(){
        return this.em;
    }



    @Override
    public Page<T> findAllWithPage(Page<T> page , Specification<T> spec) {
        StopWatch sw = new StopWatch();
        sw.start();
        page  = getPage(page, spec);
        sw.stop();
        log.info("查询耗时：{} ms", sw.getTotalTimeMillis());
        return page;
    }


    @Override
    public Page<T> findAllWithPage(Page<T> page) {
        StopWatch sw = new StopWatch();
        sw.start();
        page = getPage(page);
        sw.stop();
        log.info("查询耗时：{} ms", sw.getTotalTimeMillis());
        return page;
    }


    @Override
    public T findById(ID id, LockModeType lockModeType) {
        return em.find(this.getDomainClass(),id,lockModeType);
    }

    @Override
    public List<T> saveInBatch(Iterable<T> entities) {
        return saveInBatch(entities,MAX_SAVE_SIZE);
    }

    @Override
    public List<T> saveInBatch(Iterable<T> entities, int batchSize) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        List<T> result = new ArrayList<>();
        if (entities == null){
            return null;
        }else{
            int i = 0;
            for (Iterator var1 = entities.iterator(); var1.hasNext(); ++i){
                T entity = (T) var1.next();
                if (i != 0 && i % batchSize == 0){
                    this.flush();
                    log.debug("批量保存执行成功的数量为：{} 记录",i);
                }
                this.em.persist(entity);
                result.add(entity);
            }
            this.flush();
            stopWatch.stop();
            log.info("保存完成：{}，条记录耗时：{} ms ",i,stopWatch.getTotalTimeMillis());
            return result;
        }
    }

    @Override
    public boolean isNew(T entity) {
        return this.entityInformation.isNew(entity);
    }

    @Override
    public boolean logic(ID id) {
        Optional<T> varOptional = this.findById(id);
        if (!varOptional.isPresent()){
            throw new JpaException("删除的对象不存在");
        }
        try{
            T t = getDomainClass().newInstance();
            BeanUtils.copyProperties(varOptional.get(), t);
            Field hasDel = ReflectionUtils.findField(t.getClass(), "hasDel");
            // 私有属性保护 打开，否则无法设置对应的值，请使用spring提供的反射工具类，自己手动打开可能会被漏洞扫描到
            ReflectionUtils.makeAccessible(hasDel);
            //设置为逻辑删除值为1
            ReflectionUtils.setField(hasDel, t, DELETE_VALUE);
            this.save(t);
            return true;
        }catch (Exception e){
            throw new JpaException("实例化对象失败");
        }
    }





    //@Override
    //public T findOneById(ID id) {
    //    Optional<T> optional = this.findById(id);
    //    return optional.isPresent() ? optional.get() : null;
    //}
    //
    //@Override
    //public T findOne(List<SuperQueryParams> superQueryParams) {
    //    return null;
    //}
    //

    //
    //
    //@Override
    //public List<T> findAll(List<SuperQueryParams> superQueryParams, Predicate.BooleanOperator match) {
    //    Specification<T> specification = QueryUtils.buildSpecification(superQueryParams, match, this.getDomainClass());
    //    return this.findAll(specification);
    //}
    //
    //
    //@Override
    //public List<T> findAll(SuperQueryCustom superQueryCustom, Map<String, String> sorts) {
    //    List<String> strings = SortUtils.mapSortToList(sorts);
    //    Sort sort = null;
    //    if (!CollectionUtils.isEmpty(sorts)) {
    //        sort = QueryUtils.buildSort((String[]) strings.toArray(new String[0]));
    //    }
    //    Specification<T> specification = QueryUtils.buildSpecification(superQueryCustom, this.getDomainClass());
    //    if (sort != null){
    //        return findAll(specification, sort);
    //    }
    //    return findAll(specification);
    //}
    //
    //
    //@Override
    //public Page<T> findAllWithPage(Page<T> page, Map<String, Object> params) {
    //    StopWatch sw = new StopWatch();
    //    sw.start();
    //    page = getPage(page, QueryUtils.buildSpecification(params, this.getDomainClass()));
    //    sw.stop();
    //    log.info("查询耗时：{} ms", sw.getTotalTimeMillis());
    //    return page;
    //}
    //
    //@Override
    //public Page<T> findAllWithPage(Page<T> page, String filter) {
    //    //CriteriaBuilder builder = em.getCriteriaBuilder();
    //    //CriteriaQuery<T> query = builder.createQuery(this.getDomainClass());
    //    //Root<T> root = query.from(getDomainClass());
    //    ////RSQLVisitor<Predicate, EntityManager> visitor = new JpaPredicateVisitor<T>().defineRoot(root);
    //    ////Node rootNode = new RSQLParser().parse(filter);
    //    ////Predicate predicate = rootNode.accept(visitor, em);
    //    //if (Objects.nonNull(predicate)) {
    //    //    query.where(predicate);
    //    //}
    //    //query.select(root);
    //    //if (Objects.nonNull(page.getSort())) {
    //    //    query.orderBy(org.springframework.data.jpa.repository.query.QueryUtils.toOrders(page.getSort(), root,builder));
    //    //}
    //    //page.setRecords(getRecords(query, page.getStart(), page.getLimit()));
    //    //page.setTotalCount(new TotalCountSupplier(root,query,predicate).getAsLong());
    //    //return page;
    //    return null;
    //}
    //


    //@Override
    //public Page<T> findAllWithPage(Page<T> page,SuperQueryCustom superQueryCustom) {
    //    StopWatch sw = new StopWatch();
    //    sw.start();
    //    page = getPage(page, QueryUtils.buildSpecification(superQueryCustom, this.getDomainClass()));
    //    sw.stop();
    //    log.info("查询耗时：{} ms", sw.getTotalTimeMillis());
    //    return page;
    //}
    //

    //
    //
    //

    //
    //
    //
    //@Override
    //public void removeById(ID id) {
    //    Optional<T> varOptional = this.findById(id);
    //    if (!varOptional.isPresent()){
    //        throw new JpaException("删除的对象不存在");
    //    }
    //    try{
    //        T t = getDomainClass().newInstance();
    //        BeanUtils.copyProperties(varOptional.get(), t);
    //        Field hasDel = ReflectionUtils.findField(t.getClass(), "hasDel");
    //        // 私有属性保护 打开，否则无法设置对应的值，请使用spring提供的反射工具类，自己手动打开可能会被漏洞扫描到
    //        ReflectionUtils.makeAccessible(hasDel);
    //        //设置为逻辑删除值为1
    //        ReflectionUtils.setField(hasDel, t, DELETE_VALUE);
    //        this.save(t);
    //    }catch (Exception e){
    //        throw new JpaException("实例化对象失败");
    //    }
    //}
    //
    //@Override
    //public void removeAllById(Iterable<ID> ids) {
    //    List<T> entities = this.findAllById(ids);
    //    if (CollectionUtils.isEmpty(entities))
    //    {
    //        log.warn("批量逻辑删除不存在对象....");
    //    }
    //    try
    //    {
    //        List<T> temps = new ArrayList<T>(entities.size());
    //        for (T var: entities) {
    //            T t = getDomainClass().newInstance();
    //            BeanUtils.copyProperties(var, t);
    //            Field hasDel = ReflectionUtils.findField(t.getClass(), "hasDel");
    //            // 私有属性保护 打开，否则无法设置对应的值，请使用spring提供的反射工具类，自己手动打开可能会被漏洞扫描到
    //            ReflectionUtils.makeAccessible(hasDel);
    //            //设置为逻辑删除值为1
    //            ReflectionUtils.setField(hasDel, t, DELETE_VALUE);
    //            temps.add(t);
    //        }
    //        this.saveAll(temps);
    //    }
    //    catch (Exception e)
    //    {
    //        throw new JpaException("实例化对象失败");
    //    }
    //}
    //
    //
    //@Override
    //public void removeAll(Iterable<T> entities) {
    //    this.deleteAll();
    //}
    //
    //@Override
    //public void remove(T entity) {
    //    Field hasDel = ReflectionUtils.findField(entity.getClass(), "hasDel");
    //    ReflectionUtils.makeAccessible(hasDel);
    //    ID id = (ID)ReflectionUtils.getField(hasDel, entity.getClass());
    //    removeById(id);
    //}


    /**
     * 获取结果集
     * @auther: Herther
     * @since 1.0.0
     * @date: 2022/9/8 21:18
     */
    private List<T> getRecords(CriteriaQuery<T> query,int start, int limit){
        TypedQuery<T> typedQuery = this.em.createQuery(query);
        typedQuery.setFirstResult(start);
        typedQuery.setMaxResults(limit);
        return  typedQuery.getResultList();
    }


    /**
     * Predicate 构建
     *
     * @param root
     * @param query
     * @param builder
     * @param spec
     * @return Predicate
     * @auther: Herther
     * @since 1.0.0
     * @date: 2022/9/8 21:12
     */
    public Predicate toPredicate(Root<T> root, CriteriaQuery<T> query, CriteriaBuilder builder , Specification<T> spec){
        return spec.toPredicate(root, query, builder);
    }

    /**
     * 实际的查询
     * @auther: Herther
     * @since 1.0.0
     * @date: 2022/9/8 21:20
     */
    private Page<T> getPage(Page<T> page, Specification<T> spec) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(this.getDomainClass());
        Root<T> root = query.from(getDomainClass());
        Predicate predicate = toPredicate(root, query, builder, spec);
        if (Objects.nonNull(predicate)) {
            query.where(predicate);
        }
        query.select(root);
        if (Objects.nonNull(page.getSort())) {
            query.orderBy(org.springframework.data.jpa.repository.query.QueryUtils.toOrders(page.getSort(), root,builder));
        }
        page.setRecords(getRecords(query, page.getStart(), page.getLimit()));
        page.setTotalCount(new TotalCountSupplier(root,query,predicate).getAsLong());
        return page;
    }

    private Page<T> getPage(Page<T> page) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(this.getDomainClass());
        Root<T> root = query.from(getDomainClass());
        query.select(root);
        if (Objects.nonNull(page.getSort())) {
            query.orderBy(org.springframework.data.jpa.repository.query.QueryUtils.toOrders(page.getSort(), root,builder));
        }
        page.setRecords(getRecords(query, page.getStart(), page.getLimit()));
        page.setTotalCount(new TotalCountSupplier(root,query).getAsLong());
        return page;
    }


    @SuppressWarnings("all")
    public class TotalCountSupplier implements LongSupplier {
        private Root<T> root;
        private CriteriaQuery<?> parentQuery;
        private Predicate predicate;

        public TotalCountSupplier(Root<T> root, CriteriaQuery<?> parentQuery, Predicate predicate) {
            this.root = root;
            this.parentQuery = parentQuery;
            this.predicate = predicate;
        }

        public TotalCountSupplier(Root<T> root, CriteriaQuery<?> parentQuery) {
            this.root = root;
            this.parentQuery = parentQuery;
        }

        public long getAsLong() {
            Assert.notNull(this.parentQuery, "TypedQuery must not be null!");
            List<Long> totals = JpaDaoImpl.this.getCountQueryWithinJoin(this.root, this.parentQuery, this.predicate).getResultList();
            Long total = 0L;

            Long element;
            for(Iterator var3 = totals.iterator(); var3.hasNext(); total = total + (element == null ? 0L : element)) {
                element = (Long)var3.next();
            }

            return total;
        }
    }

    @SuppressWarnings("all")
    private TypedQuery<Long> getCountQueryWithinJoin(Root<T> root, CriteriaQuery<?> parentQuery, Predicate predicate) {
        CriteriaBuilder builder = this.em.getCriteriaBuilder();
        CriteriaQuery<Long> query = builder.createQuery(Long.class);
        if (Objects.nonNull(predicate)){
            query.where(predicate);
        }

        if (parentQuery.isDistinct()) {
            query.select(builder.countDistinct(root));
        } else {
            query.select(builder.count(root));
        }

        query.getRoots().add(root);
        root.getFetches().clear();
        query.orderBy(Collections.emptyList());
        return this.em.createQuery(query);
    }

    @SuppressWarnings("all")
    private TypedQuery<Long> getCountQueryWithinJoin(Root<T> root, CriteriaQuery<?> parentQuery) {
        CriteriaBuilder builder = this.em.getCriteriaBuilder();
        CriteriaQuery<Long> query = builder.createQuery(Long.class);
        if (parentQuery.isDistinct()) {
            query.select(builder.countDistinct(root));
        } else {
            query.select(builder.count(root));
        }

        query.getRoots().add(root);
        root.getFetches().clear();
        query.orderBy(Collections.emptyList());
        return this.em.createQuery(query);
    }


}
